﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Linq;
using System.ServiceModel;
using System.Text;
using System.Threading;

using Tradex.Connectivity.DataLog;

using Tradex;
using Tradex.ComponentModel;
using Tradex.Messages;

namespace Tradex.Connectivity {
    
    /// <summary>
    /// The ConnectorApi is used to expose the control endpoint for the ConnectorName controller to control
    /// a connector. As such, it is exposed by the Controller.
    /// </summary>
    [ServiceBehavior(
        InstanceContextMode = InstanceContextMode.Single,
        ConcurrencyMode = ConcurrencyMode.Reentrant,
        UseSynchronizationContext = false,
        IncludeExceptionDetailInFaults = true
    )]
    [DataContractFormat()]
    public sealed class ConnectorApi :
        IConnectorApi,
        IConnectorHostTarget,
        IDisposable
    {

        /// <summary>
        /// Constructor taking the host to be controlled as parameter.
        /// </summary>
        /// <param name="host"></param>
        /// <param name="key"></param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Tradex.ConnectionStatus.#ctor(System.DateTime,Tradex.ConnectionStatusCode,System.String)")]
        public ConnectorApi(ConnectorHost host, string key) {
            if (host == null) {
                throw new ArgumentException("value can not be null", "host");
            }
            if (host.HostTarget != null) {
                throw new ArgumentException("host is already connected to a ConnectorControlApi", "host");
            }
            if (string.IsNullOrWhiteSpace(key)) {
                throw new ArgumentException("value can not be null or contain only whitespace", "key");
            }
            Contract.EndContractBlock();
            _Instance = Guid.NewGuid();
            _Key = key;
            _Host = host;

            _Host.HostTarget = this;
            Status = new ConnectionStatus(DateTime.UtcNow, ConnectionStatusCode.Disconnected, "ConnectorName Initialized");

            _QueueToConnector = new ActionQueue<Message>();
            _QueueToConnectorDelegate = QueueToConnectorAction;
            _QueueToCoordination = new ActionQueue<Message>();
            _QueueToCoordinationDelegate = QueueToCoordinationAction;

            _QueueToConnector.Action = _QueueToConnectorDelegate;

            _QueueToDump = new ActionQueue<Message>();
            _QueueToDumpDelegate = QueueToDumpAction;

            _ConnectorApiFactory = new ConnectivityApiFactory();
            
            _DumpManager = null;
        }

        readonly Guid _Instance;
        readonly string _Key;
        ConnectionInfo _Info;
        ConnectionStatus _Status;
        ConnectorHost _Host;

        int _QueueToConnectorOutCount;
        int _QueueToConnectorTransfers;

        int _QueueToCoordinationOutCount;
        int _QueueToCoordinationTransfers;

        readonly ActionQueue<Message> _QueueToConnector;
        readonly Action _QueueToConnectorDelegate;
        readonly ActionQueue<Message> _QueueToCoordination;
        readonly Action _QueueToCoordinationDelegate;

        readonly ActionQueue<Message> _QueueToDump;
        readonly Action _QueueToDumpDelegate;

        ConnectivityApiFactory _ConnectorApiFactory;
        IConnectivityApi _ConnectorApi;

        Timer _Timer;
        DataLogWriter _DumpManager;

        void QueueToConnectorAction() {
            Message message;
            _QueueToConnector.Dequeue(out message);
            while (message != null) {
                _Host.ProcessMessage(message);
                _QueueToConnector.Dequeue (out message);
            }
        }

        void QueueToCoordinationAction() {
            if (_ConnectorApi == null) {
                _ConnectorApi = _ConnectorApiFactory.GetChannel();
            }
            try {
                Message[] _Buffer = _QueueToCoordination.Peek(1024);
                _ConnectorApi.SubmitMessages(_Key, _Instance, _Buffer);
                lock (this) {
                    _QueueToCoordinationOutCount += _Buffer.Length;
                    _QueueToCoordinationTransfers++;
                }
                _QueueToCoordination.TrimFirst(_Buffer.Length);
            } catch (CommunicationException) {
                _ConnectorApiFactory.DisposeChannel(_ConnectorApi);
                _ConnectorApi = null;
                _QueueToCoordination.Action = null;
            }
        }

        void QueueToDumpAction() {
            Message[] _Buffer = _QueueToDump.Peek(1024);
            if (_DumpManager != null) {
                foreach (Message message in _Buffer) {
                    _DumpManager.Process(message);
                }
            }
            _QueueToDump.TrimFirst(1024);
        }

        /// <summary>
        /// This method sends statistical and information data to the connectivity service. It should be called in regular intervals.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Console.WriteLine(System.String)")]
        public void TimerAction() {
            int queueToConnectorLength = 0;
            int queueToConnectorOutCount = 0;
            int queueToConnectorTransfers = 0;

            int queueToCoordinatioLength = 0;
            int queueToCoordinationOutCount = 0;
            int queueToCoordinationTransfers = 0;

            IConnectivityApi connectorApi = _ConnectorApiFactory.GetChannel();

            lock (this) {
                queueToConnectorLength = _QueueToConnector.Count;
                queueToConnectorOutCount = _QueueToConnectorOutCount;
                queueToConnectorTransfers = _QueueToConnectorTransfers;

                _QueueToConnectorOutCount = 0;
                _QueueToConnectorTransfers = 0;

                queueToCoordinatioLength = _QueueToCoordination.Count;
                queueToCoordinationOutCount = _QueueToCoordinationOutCount;
                queueToCoordinationTransfers = _QueueToCoordinationTransfers;

                _QueueToCoordinationOutCount = 0;
                _QueueToCoordinationTransfers = 0;
            }
            try {
                ConnectionUpdate connectionUpdate = new ConnectionUpdate(
                    queueToConnectorLength, queueToConnectorOutCount, queueToConnectorTransfers,
                    queueToCoordinatioLength, queueToCoordinationOutCount, queueToCoordinationTransfers
                );
                //if (_ConnectorApi == null) {
                //    _ConnectorApi = _ConnectorApiFactory.GetChannel();
                //}
                //_ConnectorApi.SubmitConnectorData (
                connectorApi.SubmitConnectorData(
                    _Key, _Instance, _Info, connectionUpdate, Status
                );
                _QueueToCoordination.Action = _QueueToCoordinationDelegate;
            } catch (CommunicationException) {
                // We just catch that. This is most likely an issue in the other side... maybe noone listening?
                // The system will retry a new channel next second until it works.
            } catch (ObjectDisposedException) {
                // We just catch that. This is most likely an issue in the other side... maybe noone listening?
                // The system will retry a new channel next second until it works.
            } finally {
                if (connectorApi != null) {
                    _ConnectorApiFactory.DisposeChannel(connectorApi);
                    connectorApi = null;
                }
            }
        }

        internal ConnectionStatus Status {
            get { return _Status; }
            set {
                _Status = value;
            }
        }

        #region "IConnectorControlApi"

        /// <inheritdoc />
        Guid IConnectorApi.Ping() {
            return _Instance;
        }

        /// <inheritdoc />
        public void Connect(ConnectionInfo info) {
            if (info == null) {
                throw new ArgumentException("value can not be null", "info");
            }
            Contract.EndContractBlock();

            _Info = info;
            if (info.Connector != _Host.ConnectorName) {
                throw new ArgumentException("Unable to change connector - please cycle the wrapper", "info");
            }
            if (string.IsNullOrWhiteSpace(info.DumpFolderPath )) {
                if (_DumpManager != null) {
                    _DumpManager.Dispose ();
                    _DumpManager = null;
                }
            } else {
                if (_DumpManager != null) {
                    _DumpManager.Dispose ();
                    _DumpManager = null;
                }
                _DumpManager = new DataLogWriter (info.DumpFolderPath);
            }
            _Host.Connect(info.Parameters);
        }

        /// <inheritdoc />
        public void Disconnect() {
            _Host.Disconnect();
        }

        /// <inheritdoc />
        void IConnectorApi.Shutdown() {
            _Host.Shutdown();
        }

        /// <inheritdoc />
        void IConnectorApi.SubmitMessages(Message[] messages) {
            if (messages == null) {
                throw new ArgumentException("value can not be null", "messages");
            }
            Contract.EndContractBlock();
            foreach (Message message in messages) {
                if (message != null) {
                    _QueueToConnector.Enqueue(message);
                }
            }
        }

        #endregion "IConnectorControlApi"

        #region "IConnectorHostTarget"

        /// <inheritdoc />
        void IConnectorHostTarget.SetConnectionStatus(ConnectionStatus status) {
            Status = status;
        }

        /// <inheritdoc />
        void IConnectorHostTarget.SubmitMessage(Message message) {
            _QueueToCoordination.Enqueue(message);
            _QueueToDump.Enqueue(message);
        }

        #endregion "IConnectorHostTarget"

        /// <inheritdoc />
        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        void Dispose(bool disposing) {
            if (disposing) {
                if (_Timer != null) {
                    _Timer.Dispose();
                    _Timer = null;
                }
                if (_ConnectorApi != null) {
                    _ConnectorApiFactory.DisposeChannel(_ConnectorApi);
                }
                if (_ConnectorApiFactory != null) {
                    _ConnectorApiFactory.Dispose();
                    _ConnectorApiFactory = null;
                }
                if (_Host != null) {
                    _Host.Dispose();
                    _Host = null;
                }
                if (_DumpManager != null) {
                    _DumpManager.Dispose();
                    _DumpManager = null;
                }
            }
        }

        /// <summary>
        /// Gets the key string used to identify a connector instance.
        /// </summary>
        public string ConnectorKey {
            get { return _Key; }
        }

        /// <summary>
        /// Gets the name of the connector loaded.
        /// </summary>
        public string ConnectorName {
            get { return _Host.ConnectorName; }
        }

    }

}
